package com.sunday.gateway.core.filter.log;

import com.sunday.common.core.gson.GsonUtils;
import com.sunday.common.core.remoting.rest.page.PageVO;
import com.sunday.common.core.remoting.rest.response.RestResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.reactivestreams.Publisher;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.time.Duration;
import java.time.LocalDateTime;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR;

/**
 * 访问日志响应体
 *
 * @author sunday
 * @since 2024/3/13
 */
@Slf4j
public class AccessLogHttpResponse extends ServerHttpResponseDecorator {

    private final ServerWebExchange exchange;

    private final AccessLogGatewayFilterFactory.Config config;

    private final AccessLog accessLog;

    public AccessLogHttpResponse(ServerWebExchange exchange, AccessLogGatewayFilterFactory.Config config, AccessLog accessLog) {
        super(exchange.getResponse());
        this.exchange = exchange;
        this.config = config;
        this.accessLog = accessLog;
    }

    @Override
    public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
        String originalResponseContentType = exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
        /** 响应状态码 */
        accessLog.setStatusCode(getStatusCode().value());
        /** 响应媒体类型 */
        accessLog.setResponseContentType(originalResponseContentType);
        /** 响应时间 */
        accessLog.setResponseTime(LocalDateTime.now());
        /** 执行时间 */
        accessLog.setExecuteTime(Duration.between(accessLog.getRequestTime(), accessLog.getResponseTime()).toMillis());
        // 判断响应体的类型是否为 JSON
        if (StringUtils.isNotEmpty(originalResponseContentType)
                && originalResponseContentType.contains(MediaType.APPLICATION_JSON_VALUE)) {
            // 使用DataBufferUtils.join将Flux<DataBuffer>累积为一个完整的DataBuffer
            return DataBufferUtils.join(body)
                    .publishOn(Schedulers.parallel())
                    .flatMap(dataBuffer -> {
                        // 为了处理DataBuffer中的数据，首先需要将其读取到字节数组中
                        byte[] bytes = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(bytes);
                        // 释放DataBuffer资源，避免内存泄漏
                        DataBufferUtils.release(dataBuffer);

                        if (bytes.length > config.getMaxResponseSize().toBytes()) {
                            accessLog.setResponseBody("response body is too large");
                        } else {
                            String responseContent = new String(bytes, config.getCharset());
                            /** 响应体 */
                            accessLog.setResponseBody(responseContent);
                            // 优化响应体
                            optimizeResponseBody(accessLog);
                        }
                        // 处理完毕后，将修改后的数据重新包装为一个DataBuffer
                        DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
                        // 使用writeWith方法，将处理后的数据作为响应体向下传递
                        return exchange.getResponse().writeWith(Mono.just(buffer));
                    });
        }
        // 如果传入的body不是Flux<DataBuffer>，则不进行任何处理，直接调用父类方法
        // 以默认方式继续响应体的写入操作
        return super.writeWith(body);
    }

    /**
     * 优化响应体
     * 如果响应体可以解析 RestResponse 则优化打印
     *
     * @param accessLog
     * @return
     */
    public void optimizeResponseBody(AccessLog accessLog) {
        if (StringUtils.isNotEmpty(accessLog.getResponseBody())) {
            try {
                RestResponse restResponse = GsonUtils.DEFAULT.fromJson(accessLog.getResponseBody(), RestResponse.class);
                // 当返回成功响应的时候, 才有可能出现复杂内容，所以优化打印
                if (restResponse != null && restResponse.isSuccess()) {
                    String path = accessLog.getRequestPath();
                    int lastSlashIndex = path.lastIndexOf('/');
                    if (lastSlashIndex >= 0) {
                        path = path.substring(lastSlashIndex);
                        if (path.startsWith("/list")) {
                            // 对于 /list 只设置响应体为 RestResponse 的 toString()
                            accessLog.setResponseBody(restResponse.toString());
                        } else if (path.startsWith("/page")) {
                            // 对于 /page 需要转换 data 部分并设置响应体
                            PageVO page = GsonUtils.DEFAULT.fromJson(restResponse.getData().toString(), PageVO.class);
                            restResponse.setData(page);
                            accessLog.setResponseBody(restResponse.toString());
                        }
                    }
                }
            } catch (Exception e) {
                log.error("optimizeResponseBody error: {}", e.getMessage(), e);
            }
        }
    }

}
